package com.wyp.controller;

import com.wyp.common.CodeMsg;
import com.wyp.common.Result;
import com.wyp.controller.access.AccessLimit;
import com.wyp.entity.OrderInfo;
import com.wyp.entity.User;
import com.wyp.rabbitmq.MQSender;
import com.wyp.rabbitmq.MSMesage;
import com.wyp.service.*;
import com.wyp.utils.redis.RedisService;
import com.wyp.utils.redis.prekey.pre.GoodsKey;
import com.wyp.vo.GoodsVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.List;

/**
 * @author: Oliver
 */
@Controller
@RequestMapping("/MS")
public class MSController implements InitializingBean {
    private static Logger log = LoggerFactory.getLogger(MSController.class);
    @Autowired
    UserService userService;
    @Autowired
    GoodsService goodsService;
    @Autowired
    MSGoodsService MSGoodsService;
    @Autowired
    OrderService orderService;
    @Autowired
    MSService msService;

    @Autowired
    RedisService redisService;
    @Autowired
    MQSender sender;

    /**
     * 声明本地内存标记变量
     * hashmap在扩容的时候会有线程安全问题，但是在这里，容器加载之后就初始化完成，hashmap不会发生扩容，也就不可能发生死锁问题。
     */
    private HashMap<Long, Boolean> localOverMap = new HashMap<Long, Boolean>();
    /**
     * 初始化商品数量
     */
    private static HashMap<Long, Integer> stockMap = new HashMap<Long, Integer>();

    /**
     * 系统初始化 加载秒杀商品数量到缓存
     */
    public void afterPropertiesSet() throws Exception {
        List<GoodsVO> goodsList = goodsService.getGoodsVoList();
        if (goodsList == null) {
            return;
        }
        for (GoodsVO goods : goodsList) {
            stockMap.put(goods.getId(), goods.getStockCount());
            redisService.set(GoodsKey.GOODS_STOCK_ID, "" + goods.getId(), goods.getStockCount());
            //容器初始化，设置为false
            localOverMap.put(goods.getId(), false);
        }
    }

    /**
     * 秒杀操作
     * QPS 667                1920
     * 500 * 10 线程组          1000*10
     * QPS  1500                未测试
     *
     * @return
     */
    @RequestMapping(value = "/{path}/do_MS", method = RequestMethod.POST)
    @ResponseBody
    public Result<Integer> doMS(User user, @RequestParam("goodsId") Long goodsId,
                                @PathVariable("path") String path) {
        log.info(user.getNickname());
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        //检验秒杀地址是否合适
        boolean check = msService.checkPath(user, goodsId, path);
        if (!check) {
            //校验不通过直接返回错误信息，请求参数有误
            return Result.error(CodeMsg.REQUEST_ILLEGAL);
        }
        /**标记减少redis访问，实际上只有10个商品，但是每次都要去redis中去判断库存，当商品数量为0时还是会去访问，需要修改为，当商品数量为0时直接返回秒杀失败。
         减少redis交互，减少网络开销
         */
        //内存标记，减少redis访问
        boolean over = localOverMap.get(goodsId);
        if (over) {
            return Result.error(CodeMsg.MIAO_SHA_OVER);
        }
        //使用redis进行预减少库存
        Long stock = redisService.decr(GoodsKey.GOODS_STOCK_ID, "" + goodsId);//10
        //如果小于0，秒杀完毕，直接返回，减少后续交互数据库
        if (stock <= 0) {  //在并发情况下stock可能被减为负数：线程执行decr方法，执行结束上一个想成还没有做完赋值判断操作
            localOverMap.put(goodsId, true);
            return Result.error(CodeMsg.MIAO_SHA_OVER);
        }
        GoodsVO goods = new GoodsVO();
        goods.setId(goodsId);
        OrderInfo orderInfo = orderService.getOrderByUserIdAndGoodsId(user, goods);
        if (orderInfo != null) {
            return Result.error(CodeMsg.REPEATE_MS);
        }
        //入队
        MSMesage mm = new MSMesage();
        mm.setUser(user);
        mm.setGoodsId(goodsId);
        sender.sendMSMessage(mm);
        return Result.success(0);//排队中
    }

    /**
     * orderId：成功
     * -1：秒杀失败
     * 0： 排队中
     */
    @RequestMapping(value = "/result", method = RequestMethod.GET)
    @ResponseBody
    public Result<Long> MSResult(Model model, User user,
                                 @RequestParam("goodsId") long goodsId) {
        model.addAttribute("user", user);
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        long result = msService.getMSResult(user.getId(), goodsId);
        return Result.success(result);
    }

    /**
     * 优化之前的版本  解决在这一版本中商品数量减为负数；订单数量超过商品数量两个主要问题，还解决了同一用户不能重复秒杀问题。
     *
     * @param user
     * @param goodsId
     * @return
     */
    @RequestMapping("do_ms")
    @ResponseBody
    public Result<OrderInfo> doms(User user, @RequestParam("goodsId") Long goodsId) {
        log.info(user.getNickname());
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        //判断库存--判断该用户是否已经秒杀过该商品--进行秒杀（写入订单表和减库存--原子性）
        GoodsVO goods = goodsService.getGoodsVOById(goodsId);
        int stock = goods.getStockCount();
        if (stock <= 0) {
            return Result.error(CodeMsg.MIAO_SHA_OVER);
        }
        OrderInfo orderInfo = orderService.getOrderByUserIdAndGoodsId(user, goods);
        if (orderInfo != null) {
            return Result.error(CodeMsg.REPEATE_MS);
        }
        //进行生成订单和减库存
        OrderInfo order = msService.MS(user, goods);
        return Result.success(order);
    }

    @AccessLimit(seconds = 5, maxCount = 5, needLogin = true)
    @RequestMapping("path")
    @ResponseBody
    public Result<String> path(User user, @RequestParam("goodsId") Long goodsId,
                               @RequestParam(value = "verifyCode", defaultValue = "0") Integer verifyCode) {
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }

        /**
         //        //防刷限流操作 --通过redis进行记录每个用户访问该接口url在规定时间次数，进而达到限制作用
         //        Integer num = redisService.get(MSKey.MS_ACCESS_NUM, "" + user.getId(), Integer.class);
         //        if (num == null) {
         //            //第一次访问
         //            redisService.set(MSKey.MS_ACCESS_NUM, "" + user.getId(), 1);
         //        } else if (num < 5) {
         //            redisService.incr(MSKey.MS_ACCESS_NUM, "" + user.getId());
         //        } else {
         //            return Result.error(CodeMsg.ACCESS_LIMIT_REACHED);
         //        }
         **
         * 将防刷限流操作抽取出来定义为注解，然后使用拦截器进行拦截限流操作
         * */

        //校验验证码
        if (verifyCode == null) {
            return Result.error(CodeMsg.VERIFY_CODE_EMPTY);
        }
        boolean check = msService.checkVerifyCode(user, goodsId, verifyCode);
        if (!check) {
            return Result.error(CodeMsg.REQUEST_ILLEGAL);
        }

        String path = msService.createPath(user, goodsId);
        return Result.success(path);
    }

    @RequestMapping("verifyCode")
    @ResponseBody
    public Result<String> getMSVerifyCod(HttpServletResponse response, User user, @RequestParam("goodsId") Long goodsId) {
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        //使用流将图片输出出去
        try {
            BufferedImage image = msService.createVerifyCode(user, goodsId);
            ServletOutputStream out = response.getOutputStream();
            ImageIO.write(image, "JPEG", out);
            out.flush();
            out.close();
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(CodeMsg.MS_FAIL);
        }
    }

    /**
     * 获取初始的商品秒杀数量
     */
    public static int getGoodsStockOriginal(long goodsId) {
        Integer stock = stockMap.get(goodsId);
        if (stock == null) {
            return 0;
        }
        return stock;
    }
}
